﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using LayersDotNet.DataAccess.Interface;
using LayersDotNet.Entity;
using System.Data;
using System.Data.Objects.DataClasses;
using System.Data.Objects;
using System.Reflection;
using System.Linq.Expressions;
using System.Data.Common;

namespace LayersDotNet.DataAccess.EntityFx
{
    /// <summary>
    /// Base Data Access implementing Core methods
    /// </summary>
    /// <typeparam name="T"></typeparam>
    public class BaseDA<T> : IBaseDA<T> where T : class
    {
        public object DatabaseContext
        {
            get
            {
                return EntityContext;
            }
            set
            {
                EntityContext = (TestDbEntities)value;
            }
        }

        public TestDbEntities EntityContext
        {
            get;
            set;
        }

        public IDataAccessAccessPoint DataAccess
        {
            get;
            set;
        }

        public bool Insert(T t)
        {
            EntityContext.AddObject(t.GetType().Name, t);
            bool result = EntityContext.SaveChanges() == 0;

            return result;
        }

        private string GetEntitySetName(T t)
        {
            string name = t.GetType().Name;

            return name;
        }

        public bool Insert(T t, out int newId)
        {
            newId = -1;

            EntityContext.AddObject(t.GetType().Name, t);
            bool result = EntityContext.SaveChanges() == 0;

            foreach (PropertyInfo pinfo in t.GetType().GetProperties())
            {
                EdmScalarPropertyAttribute attribute = pinfo.GetCustomAttributes(true).OfType<EdmScalarPropertyAttribute>().FirstOrDefault();
                if ((attribute != null) && (attribute.EntityKeyProperty))
                {
                    newId = (int)pinfo.GetValue(t, null);
                    break;
                }
            }

            return result;
        }

        public bool Update(T t)
        {
            if ((t as EntityObject).EntityState == EntityState.Detached)
                EntityContext.AttachTo(t.GetType().Name, t);

            EntityContext.ObjectStateManager.ChangeObjectState(t, System.Data.EntityState.Modified);

            bool result = false;

            try
            {
                result = EntityContext.SaveChanges() == 1;
                EntityContext.Detach(t);
            }
            catch (OptimisticConcurrencyException oe)
            {
                result = false;
            }

            return result;
        }

        public bool Delete(T t)
        {
            if ((t as EntityObject).EntityState == EntityState.Detached)
                EntityContext.AttachTo(t.GetType().Name, t);

            EntityContext.ObjectStateManager.ChangeObjectState(t, System.Data.EntityState.Deleted);

            bool result = false;

            try
            {
                result = EntityContext.SaveChanges() == 1;
            }
            catch (OptimisticConcurrencyException e)
            {
                // Handle Exception
                result = false;
            }

            return result;
        }

        public IEnumerable<T> GetAll()
        {
            ObjectSet<T> objectSet = GetObjectSetPropertyInfo().GetValue(EntityContext, null) as ObjectSet<T>;

            IEnumerable<T> result = objectSet.ToList<T>();

            return result;
        }

        public IList<T> GetAllAsList()
        {
            IList<T> result = GetAll().ToList<T>();

            return result;
        }

        public HashSet<T> GetAllAsHashSet()
        {
            HashSet<T> result = new HashSet<T>();
            foreach (T t in GetAll())
                result.Add(t);

            return result;
        }

        private PropertyInfo GetObjectSetPropertyInfo()
        {
            Type type = EntityContext.GetType();
            string name = typeof(T).Name;
            PropertyInfo result = type.GetProperty(name);

            if (result == null)
                throw new ApplicationException(string.Format("Invalid {0} in context with ObjectSet<> return type!", name));

            return result;
        }

        public bool DeleteAll()
        {
            var entityConnection = (System.Data.EntityClient.EntityConnection)EntityContext.Connection;
            DbConnection conn = entityConnection.StoreConnection;
            
            DbCommand command = conn.CreateCommand();
            command.CommandText = "DELETE FROM " + typeof(T).Name;
            bool result = command.ExecuteNonQuery() == 0;

            return result;
        }

        public IEnumerable<T> Where(Expression<Func<T, bool>> expression)
        {
            ObjectSet<T> objectSet = GetObjectSetPropertyInfo().GetValue(EntityContext, null) as ObjectSet<T>;
            IEnumerable<T> result = objectSet.Where(expression);

            return result;
        }

        public IList<T> WhereAsList(Expression<Func<T, bool>> expression)
        {
            ObjectSet<T> objectSet = GetObjectSetPropertyInfo().GetValue(EntityContext, null) as ObjectSet<T>;
            IList<T> result = objectSet.Where(expression).ToList<T>();

            return result;
        }

        public HashSet<T> WhereAsHashSet(Expression<Func<T, bool>> expression)
        {
            ObjectSet<T> objectSet = GetObjectSetPropertyInfo().GetValue(EntityContext, null) as ObjectSet<T>;

            HashSet<T> result = new HashSet<T>();
            foreach (T obj in objectSet.Where(expression))
                result.Add(obj);

            return result;
        }

        public int Count()
        {
            ObjectSet<T> objectSet = GetObjectSetPropertyInfo().GetValue(EntityContext, null) as ObjectSet<T>;

            int result = objectSet.Count<T>();

            return result;
        }

        public int Count(Expression<Func<T, bool>> expression)
        {
            ObjectSet<T> objectSet = GetObjectSetPropertyInfo().GetValue(EntityContext, null) as ObjectSet<T>;

            int result = objectSet.Count<T>(expression);

            return result;
        }

        public bool IsEmpty()
        {
            bool result = this.Count() > 0;

            return result;
        }

        public bool IsEmpty(Expression<Func<T, bool>> expression)
        {
            bool result = this.Count(expression) > 0;

            return result;
        }
    }
}
